package dustconf;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import p2pmodule.Peer;

/** TODO: Right now everything is done "manually" reading a file. This is quite
 * ugly, this should be done defining a grammar and using some tools to
 * generate a parser for it. Flex and Bison are your friends!. Another idea is
 * create an XML namespace to define the grammar and use SAX or DOM to parser
 * it... I prefer the first option. **/

/**
 * Class that represents the configuration of Dust. This is a module used by
 * every component/module of Dust. It is initialized at the start and then
 * every time a component want to know something of its configuration should
 * call to this module.
 * 
 * @author ole
 */
public class DustConf {
	// Class constants.
	// The variable names that Dust understand. Don't change their order, or
	// if you do so, change the variables below that makes references to the
	// positions of this array.
	public static final String[] varnames = { "serialized_path",
											  "shared_folder",
											  "temp_folder",
											  "peers_file",
											  "keys_folder",
											  "images_folder",
											  "gnutella_port"
											};
	public final String DEFAULT_CONF_FILE = "dust.conf";
	private final String SERIALIZED_BLOGS_PATH_VARNAME = varnames[0];
	private final String SHARED_FOLDER_VARNAME = varnames[1];
	private final String TEMP_FOLDER_VARNAME = varnames[2];
	private final String PEERS_FILE_VARNAME = varnames[3];
	private final String KEYS_FOLDER_VARNAME = varnames[4];
	private final String IMAGES_FOLDER_VARNAME = varnames[5];
	private final String GNUTELLA_PORT_VARNAME = varnames[6];
	private final String IP_PORT_SEPARATOR = ":";
	private final String COMMENTARY_INITIATOR = "#";
	
	// Class attributes.
	private static String serialized_blogs_path = null;
	private static String shared_folder = null;
	private static String temp_folder = null;
	private static String peers_file = null;
	private static String keys_folder = null;
	private static String images_folder = null;
	private static List<Peer> peers = null;
	private static int gnutella_port = -1;
	
	/*
	 * Public helping classes.
	 */
	
	/** TODO: The error message is not being shown when an exception occurs. **/
	/**
	 * Class to represent a syntax error parsing the configuration file.
	 */
	public class DustSyntaxException extends Exception {
		private static final long serialVersionUID = 1;
		
		public DustSyntaxException(String message) {
			new Exception(message);
		}
	}
	
	/*
	 * Private methods of DustConf class.
	 */
	
	/**
	 * This method wraps all the logic of open a file and get all the lines
	 * that are valid entries since these configuration files accepts lines of
	 * commentaries (starting with '#') and empty lines (lines with blank
	 * characters).
	 * 
	 * @param fname
	 * 		The filename where we will read.
	 * @throws IOException
	 */
	private ArrayList<String> getConfLines(String fname) throws IOException {
		File fd;
		FileReader freader;
		BufferedReader breader;
		String line;
		ArrayList<String> result;
		
		// Check if the file exists.
		fd = new File(fname);
		if (!fd.exists())
			throw new FileNotFoundException("File " + fname + " doesn't " +
											"exists.");
		
		// Create a BufferedReader from that file to make possible read one
		// line at a time.
		freader = new FileReader(fd);
		breader = new BufferedReader(freader);
		
		// Read the whole file and ignore those lines that are commentaries.
		result = new ArrayList<String>();
		line = breader.readLine();
		while (line != null) {
			// If the line is not only blank characters or if the line doesn't
			// starts with the commentary character (maybe preceded by blanks)
			// treat it as a configuration line. Also if the line is not empty. 
			if (!line.matches("\\s+") &&
				!line.matches("^\\s*" + COMMENTARY_INITIATOR + ".*$") &&
				!line.isEmpty()) {
				result.add(line);
			}
			
			// Read the next line.
			line = breader.readLine();
		}
		
		return result;
	}
	
	/**
	 * This method checks if a variable exists and fill its value in the
	 * DustConf instance.
	 * 
	 * @param name
	 * 		The name of the configuration variable.
	 * @param value
	 * 		The value readed for this configuration variable.
	 * @throws DustSyntaxException
	 */
	private void checkAndFill(String name, String value)
	throws DustSyntaxException {
		// Check if we know that variable name and fill it in the class
		// attributes if so.
		if (name.equals(SERIALIZED_BLOGS_PATH_VARNAME))
			serialized_blogs_path = value;
		else if (name.equals(SHARED_FOLDER_VARNAME))
			shared_folder = value;
		else if (name.equals(TEMP_FOLDER_VARNAME))
			temp_folder = value;
		else if (name.equals(PEERS_FILE_VARNAME))
			peers_file = value;
		else if (name.equals(KEYS_FOLDER_VARNAME))
			keys_folder = value;
		else if (name.equals(IMAGES_FOLDER_VARNAME))
			images_folder = value;
		else if (name.equals(GNUTELLA_PORT_VARNAME))
			gnutella_port = new Integer(value);
		else
			throw new DustSyntaxException("Unknown variable \"" + name + "\"");
	}
	
	/** TODO: Right now this method assumes that the file has real IPs and
	 *  ports, it has no strict checks. **/
	/**
	 * Function that builds a List of Peers from the information in the peers
	 * file.
	 * 
	 * @param fpeers
	 * 		The file where the IPs and ports are written.
	 * @throws DustSyntaxException
	 */
	private List<Peer> buildPeersList(String fpeers)
	throws IOException, DustSyntaxException {
		ArrayList<String> lines;
		List<Peer> list;
		String ip;
		Integer port;
		String[] ip_port;
		
		lines = getConfLines(fpeers);
		list = new ArrayList<Peer>();
		for (String line: lines) {
			ip_port = line.split(IP_PORT_SEPARATOR);
			
			// Check if there are 2 elements (the IP and the port).
			if (ip_port.length != 2)
				throw new DustSyntaxException("Syntax error in file " +
											  fpeers + " in line \"" + line +
											  "\"");
			
			// Add the pair <ip, port> to the HashMap.
			ip = ip_port[0];
			port = new Integer(ip_port[1]);
			list.add(new Peer(ip, port));
		}
		
		return list;
	}
	
	/*
	 * Public methods of DustConf class.
	 */
	
	/**
	 * Constructor class. It try to search for the default configuration file
	 * and read it.
	 * 
	 * @throws IOException 
	 * @throws DustSyntaxException 
	 */
	public DustConf() throws IOException, DustSyntaxException {
		new DustConf(DEFAULT_CONF_FILE);
	}
	
	/** TODO: Use a grammar and a parser to do all the parsing work. **/
	/**
	 * Constructor class. Read and prepare the Dust configuration from the
	 * specified file.
	 * 
	 * A Dust configuration file consist of lines of the form
	 * VARIABLE_NAME = VARIABLE_VALUE
	 * 
	 * Right now this file is not parsed as it has to be, in the future this
	 * will be improved, by now don't try weird things :).
	 * 
	 * @param fname:
	 * 		The filename from where read the configuration.
	 * @throws IOException 
	 * @throws DustSyntaxException 
	 */
	public DustConf(String fname) throws IOException, DustSyntaxException {
		ArrayList<String> lines;
		String name, value;
		String[] name_value;
		
		lines = getConfLines(fname);
		for (String line: lines) {
			
			// Each line must be of the form VARIABLE_NAME = "VARIABLE_VALUE"
			name_value = line.split("\\s*=\\s*");
			if (name_value.length != 2)
				throw new DustSyntaxException("Syntax error in file " + fname +
											  " in line \"" + line + "\"");
			
			// Get the name of the variable and its value.
			name = name_value[0];
			value = name_value[1];
			
			// Check if the variable readed is a valid variable and if so
			// record its value in the DustConf instance.
			checkAndFill(name, value);
		}
		
		// Read the peers file and build the table of peers.
		if (peers_file == null)
			throw new RuntimeException("Variable peers_file wasn't "+
									   "specified and it is mandatory.");
		peers = buildPeersList(peers_file);
	}
	
	/**
	 * Returns the port where the Gnutella servant should stay listen.
	 */
	public static int getGnutellaPort() {
		return gnutella_port;
	}
	
	/**
	 * Return a List of Hosts that are the known peers in the Peers file.
	 */
	public static List<Peer> getPeers() {
		return peers;
	}
	
	/**
	 * Return the file that was configured to contain the peers in the P2P
	 * network.
	 */
	public static String getPeersFile() {
		return peers_file;
	}
	
	/**
	 * Returns the path that was configured to contain the blogs serialized.
	 */
	public static String getSerializedBlogsPath() {
		return serialized_blogs_path;
	}
	
	/**
	 * 
	 */
	public static String getSharedFolder() {
		return shared_folder;
	}
	
	/**
	 * 
	 */
	public static String getTempFolder() {
		return temp_folder;
	}
	
	/**
	 * 
	 */
	public static String getKeysFolder() {
		return keys_folder;
	}
	
	/**
	 * 
	 */
	public static String getImagesFolder() {
		return images_folder;
	}
}
